/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.j4sc.submission.audio;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.sound.sampled.AudioFileFormat.Type;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;

/**
 * The Audio to be sent in the submission to the service.
 * <p>
 * On construction, the audio is checked to ensure it is G.711 a-law or u-law
 * encoded, 8000 MHz sample rate and mono channel
 * <p>
 * The audio length is also checked to ensure it is 30 seconds or less
 * <p>
 * 
 * @author Raj Patel, Dariusz Lorenc
 */
public class Audio {

	private static final String TOO_LONG_MESSAGE = "The audio file must be no more than 30 seconds in length";
	private static final String INVALID_AUDIO_MESSAGE = "The audio file must be encoded as a " +
				"G.711 A-Law 8 kHz mono WAVE file or a G.711 u-Law 8 kHz mono WAVE file";
	private static final int MAX_AUDIO_LENGTH = 30;
	private static final int SAMPLE_RATE = 8000;
	private static final int MONO = 1;
	
	private byte[] audioBytes;

	/**
	 * Construct a new instance
	 * 
	 * @param audioBytes
	 *            the audio bytes
	 * @throws IOException
	 * @throws InvalidAudioException
	 *             if the audio is not encoded correctly or invalid length
	 */
	public Audio(byte[] audioBytes) throws IOException, InvalidAudioException {
		this(new ByteArrayInputStream(audioBytes));
	}

	/**
	 * Construct a new instance
	 * 
	 * @param file
	 *            the audio file
	 * @throws IOException
	 * @throws InvalidAudioException
	 *             if the audio is not encoded correctly or invalid length
	 */
	public Audio(File file) throws IOException, InvalidAudioException {
		this(new FileInputStream(file));
	}

	/**
	 * Construct a new instance
	 * 
	 * @param stream
	 *            the audio stream
	 * @throws IOException
	 * @throws InvalidAudioException
	 *             if the audio is not encoded correctly or invalid length
	 */
	public Audio(InputStream stream) throws IOException, InvalidAudioException {
		BufferedInputStream bufferedInputStream = new BufferedInputStream(stream);
		AudioFileFormat fileFormat = getAudioFileFormat(bufferedInputStream);

		if(!isValidAudio(fileFormat)) {
			throw new InvalidAudioException(INVALID_AUDIO_MESSAGE);
		} 
		if (!isValidLength(fileFormat)) {
			throw new InvalidAudioException(TOO_LONG_MESSAGE);
		}
		
		audioBytes = IOUtils.toByteArray(bufferedInputStream);
	}

	/**
	 * The audio as a BASE 64 encoded string chunked to 76 character blocks
	 * 
	 * @return the base 64 string
	 */
	public String toBase64() {
		return new String(Base64.encodeBase64(audioBytes, true));
	}
	
	private boolean isValidAudio(AudioFileFormat fileFormat) throws IOException, InvalidAudioException {
		AudioFormat format = fileFormat.getFormat();
		if(isWave(fileFormat.getType()) && isAlawOrUlaw(format.getEncoding())
				&& isMonoChannel(format) && isValidSampleRate(format)) {
			return true;
		} else {
			return false;
		}
	}

	private AudioFileFormat getAudioFileFormat(BufferedInputStream stream)
			throws IOException, InvalidAudioException {
		AudioFileFormat fileFormat;
		try {
			fileFormat = AudioSystem.getAudioFileFormat(stream);
		} catch (UnsupportedAudioFileException e) {
			throw new InvalidAudioException(INVALID_AUDIO_MESSAGE, e);
		}
		return fileFormat;
	}
	
	private boolean isMonoChannel(AudioFormat format) {
		return format.getChannels() == MONO;
	}

	private boolean isAlawOrUlaw(AudioFormat.Encoding encoding) {
		return (encoding == AudioFormat.Encoding.ALAW ||
				encoding == AudioFormat.Encoding.ULAW);
	}
	
	private boolean isValidSampleRate(AudioFormat format) {
		return format.getSampleRate() == SAMPLE_RATE;
	}
	
	private boolean isValidLength(AudioFileFormat fileFormat) {
		float audioLength = fileFormat.getFrameLength() / fileFormat.getFormat().getSampleRate();
		return audioLength <= MAX_AUDIO_LENGTH;
	}

	private boolean isWave(Type type) {
		return type == Type.WAVE;
	}
}
